package cn.edu.lsnu.mbti.service.impl;

import cn.edu.lsnu.mbti.common.Constants;
import cn.edu.lsnu.mbti.dto.DepartmentDto;
import cn.edu.lsnu.mbti.dto.UserDto;
import cn.edu.lsnu.mbti.entity.*;
import cn.edu.lsnu.mbti.entity.vo.DepartmentVo;
import cn.edu.lsnu.mbti.exception.CustomerException;
import cn.edu.lsnu.mbti.mapper.CategoryMapper;
import cn.edu.lsnu.mbti.mapper.DepartmentMapper;
import cn.edu.lsnu.mbti.service.*;
import cn.edu.lsnu.mbti.util.DelRepeatUtils;
import cn.edu.lsnu.mbti.util.MajorUtils;
import cn.edu.lsnu.mbti.util.RedisUtil;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.lang.TypeReference;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeanUtils;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Service
@Slf4j
public class DepartmentServiceImpl extends ServiceImpl<DepartmentMapper, Department> implements DepartmentService {

    public static final String departmentKey = "MBTI:DEPARTMENT:ALL";
    public static final String recommendDepartmentKey = "MBTI:RECOMMEND_DEPARTMENT:";
    public static final String saveDepartmentLock = "LOCK:SAVE:DEPARTMENT";
    @Lazy
    @Resource
    private UserService userService;
    @Resource
    private PersonalityService personalityService;
    @Resource
    private MajorService majorService;
    @Resource
    private DepartmentUserService departmentUserService;
    @Resource
    private RedisUtil redisUtil;
    @Resource
    private RedissonClient redissonClient;
    @Resource
    private CategoryMapper categoryMapper;
    @Resource
    private DepartmentMapper departmentMapper;


    /**
     * vue后台管理保存学科
     * @param department
     * @return
     */
    @Override
    @Transactional
    public String saveDepartment(Department department) {
        // 获取锁
        RLock lock = redissonClient.getLock(saveDepartmentLock);
        try {
            // 锁对象尝试获取锁，waitTime：等待时间 leaseTime：key的失效时间
            boolean isLock = lock.tryLock(10, 30, TimeUnit.SECONDS);
            if (isLock){
                try {
                    int result = 0;
                    //判断id是否为空
                    if (StrUtil.isBlank(department.getId())){
                        //空，新增院校
                        result = departmentMapper.insert(department);
                    }else{
                        //不为空，修改院校
                        result = departmentMapper.updateById(department);
                    }
                    if (result > 0){
                        return "保存成功";
                    }
                } catch (Exception e) {
                    throw new CustomerException("保存失败，请稍后重试");
                }finally {
                    // 释放锁
                    lock.unlock();
                }
            }
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        throw new CustomerException("保存失败，请稍后重试");
    }

    /**
     * vue后台管理分页查询学科列表
     * @param departmentVo
     * @return
     */
    @Override
    public Page<DepartmentDto> queryPageList(DepartmentVo departmentVo) {
        //构造查询条件
        LambdaQueryWrapper<Department> wrapper = new LambdaQueryWrapper<>();
        wrapper.like(Department::getName,departmentVo.getName());

        Page<Department> departmentPage = new Page<>(departmentVo.getPageNum(), departmentVo.getPageSize());

        //分页查询
        departmentPage= this.page(departmentPage, wrapper);

        //对象复制
        Page<DepartmentDto> departmentDtoPage = new Page<>();
        BeanUtils.copyProperties(departmentPage,departmentDtoPage,"records");

        //封装属性
        departmentDtoPage.setRecords(departmentPage.getRecords().stream().map(department -> {
            DepartmentDto departmentDto = new DepartmentDto();
            BeanUtils.copyProperties(department,departmentDto);
            LambdaQueryWrapper<Major> majorWrapper = new LambdaQueryWrapper<>();
            majorWrapper.eq(Major::getDepartmentId, department.getId());
            List<Major> majorList = Optional.ofNullable(majorService.list(majorWrapper))
                    .orElse(new ArrayList<>());
            if (majorList.size() > 0){
                majorList = majorList.stream().map(major -> {
                    major.setCategoryName(categoryMapper.selectById(major.getCategoryId()).getName());
                    return major;
                }).collect(Collectors.toList());
            }
            //对象复制
            departmentDto.setMajorList(majorList);

            return departmentDto;
        }).collect(Collectors.toList()));

        return departmentDtoPage;
    }

    /**
     * 展示推荐的学科
     * @param userId
     * @return
     */
    @Override
    @Transactional
    public List<DepartmentDto> selectDepartmentByRecommend(String userId) {
        List<Department> departments;
        //根据key查询redis缓存
        String jsonStr = (String) redisUtil.get(recommendDepartmentKey+userId);
        //判断value是否为空
        if (StrUtil.isNotBlank(jsonStr)){
            //缓存有数据
            List<DepartmentDto> dtoList = JSONUtil.toBean(jsonStr, new TypeReference<List<DepartmentDto>>() {
            }, true);
            //返回缓存
            return dtoList;
        }

        // 查询当前userId是否合法
        User user = userService.getById(userId);
        if (BeanUtil.isEmpty(user)){
            // 不合法，缓存空对象解决缓存穿透问题
            redisUtil.set(recommendDepartmentKey+userId, "", Constants.EXPIRE_WEEK);
            return new ArrayList<>();
        }

        //缓存没有数据
        List<Department> departmentList = new ArrayList<>();
        String expectDepartmentName = ""; //想报考的方向

        //根据id查询当前用户信息
        UserDto userDto = userService.selectForEdit(userId);

        //设置匹配值
        expectDepartmentName = userDto.getExpectDepartmentName();

        //根据预期学科匹配
        //构造查询学科的构造条件
        LambdaQueryWrapper<Department> departmentWrapper = new LambdaQueryWrapper<>();
        departmentWrapper.eq(Department::getName,expectDepartmentName);

        //查询预期学科
        Department expectDepartment = this.getOne(departmentWrapper);

        //封装expectDepartment
        departmentList.add(expectDepartment);

        //根据测试人格进行匹配
        //根据personalityId查询personality对象
        Personality personality = personalityService.getById(userDto.getPersonalityId());

        //获取recommendMajor
        String recommendMajor = personality.getRecommendMajor();
        String[] majorNames = MajorUtils.getMajor(recommendMajor);   //获得每个推荐专业名字

        //对每个专业进行遍历，获取学科信息
        for (String majorName : majorNames) {//工程 计算机 金融 物理 化学 生物
            //构造查询专业构造器
            LambdaQueryWrapper<Major> majorWrapper = new LambdaQueryWrapper<>();

            //构造条件
            majorWrapper.like(Major::getName,majorName);

            //查询专业
            List<Major> majorList = majorService.list(majorWrapper);

            //对查询出来的专业集合进行遍历
            for (Major major : majorList) {
                //根据departmentId查询学科信息
                Department department = this.getById(major.getDepartmentId());

                //封装进departmentList
                departmentList.add(department);
            }
        }

        //根据自己的专业查询学科信息
        //根据majorId查询专业信息
        Major ownMajor = majorService.getById(userDto.getMajorId());

        //根据departmentId查询学科信息

        Department ownDepartment = this.getById(ownMajor.getDepartmentId());

        //封装ownDepartment
        departmentList.add(ownDepartment);

        //对departmentList进行去重
        departments = DelRepeatUtils.delRepeatList(departmentList);

        //将departments保存到department_user表中
        for (Department department : departments) {
            //封装对象
            DepartmentUser departmentUser = new DepartmentUser();
            departmentUser.setUserId(userId);
            departmentUser.setDepartmentId(department.getId());

            //保存
            departmentUserService.save(departmentUser);
        }

        //对每个departments进行设置departmentDto
        List<DepartmentDto> departmentDtoList = departments.stream().map(item -> {
            DepartmentDto dto = new DepartmentDto();
            //通过departmentId查询专业集合
            LambdaQueryWrapper<Major> majorWrapper = new LambdaQueryWrapper<>();
            majorWrapper.eq(Major::getDepartmentId, item.getId());
            List<Major> majorList = majorService.list(majorWrapper);
            //对象复制
            BeanUtil.copyProperties(item, dto);
            dto.setMajorList(majorList);

            return dto;
        }).collect(Collectors.toList());

        //将departments存入redis
        redisUtil.set(recommendDepartmentKey+userId, JSONUtil.toJsonStr(departmentDtoList), Constants.EXPIRE_WEEK);

        return departmentDtoList;
    }

    /**
     * 查询院系信息
     * redis做缓存
     * @return
     */
    @Override
    public List<Department> queryList() {
        List<Department> departmentList;
        //根据key查询redis中是否缓存了数据
        String jsonStr = (String) redisUtil.get(departmentKey);
        if (StrUtil.isNotBlank(jsonStr)){
            //不为空，redis中有改key的数据，直接转成对象返回
            departmentList = JSONUtil.toBean(jsonStr, new TypeReference<List<Department>>() {
            }, true);
            return departmentList;
        }
        departmentList = this.list();
        redisUtil.set(departmentKey,JSONUtil.toJsonStr(departmentList),604800);
        return departmentList;
    }
}
